<?php

#===================================
#===Author  : qian.yu            ===
#===Email   : foolfish@gmail.com ===
#===QQ      : 9097939            ===
#===Homepage: www.fishlib.cn     ===
#===================================

#===1.0.3(2010-09-09): persistent=2 mode
#===1.0.2(2010-05-26): rename get_auto_id to lib_get_auto_id
#===1.0.1(2010-05-23): allow_ids auto_id_mysql_connect
#===1.0.0(2010-05-23): first release

#MASTER SQL
#CREATE TABLE IF NOT EXISTS `res` (`k` smallint(5) unsigned NOT NULL,`id` int(10) unsigned NOT NULL,PRIMARY KEY (`k`)) ENGINE=MyISAM

#SLAVE SQL
#CREATE TABLE IF NOT EXISTS `res` (`k` smallint(5) unsigned NOT NULL,`id` int(10) unsigned NOT NULL,`num` int(10) unsigned NOT NULL,PRIMARY KEY (`k`)) ENGINE=MyISAM

/*
$server_master=array(
	"auto_id_type"=>"master",
	"host"=>"127.0.0.1:3306",
	"usr"=>"root",
	"pass"=>"",
	"auto_id_table"=>"test.res",
	"persistent"=>false,           #可选，默认false
	"weight"=>10,                  #可选，默认1，当与其他server组成集群时排序用，数字越大越优先
);

$server_http=array(
	"auto_id_type"=>"http",
	"url"=>"http://www.tsjie.com/autoid/master.php",
	"auth"=>"abc",
	"timeout"=>15,                 #可选，默认false
	"weight"=>100,                 #可选，默认1，当与其他server组成集群时排序用，数字越大越优先
);

$server_backup=array(
	"auto_id_type"=>"slave",
	"host"=>"127.0.0.1:3306",
	"usr"=>"root",
	"pass"=>"",
	"auto_id_table"=>"test.res_slave_backup",
	"persistent"=>false,
	"capacity"=>array(
		"default"=>array(2000,1000), #默认的缓存数量，缓存2000个，低于1000个的时候充值
		"1001"=>array(1000,100),#单独设定的缓存数量 1001 缓存1000 个，低于100个时候充值
	),
	"parents"=>array($server_master),#当该节点没有id的时候去哪里拿id,按权值顺序挨个尝试
	"weight"=>10,                    #可选，默认1，当与其他server组成集群时排序用，数字越大越优先
	"max_retry"=>4,                  #可选，默认2，不需要设定太大，某小概率事件的尝试次数，细节请咨询开发者
);

$server_current=array(
	"auto_id_type"=>"slave",
	"host"=>"127.0.0.1:3306",
	"usr"=>"root",
	"pass"=>"",
	"auto_id_table"=>"test.res_slave_current",
	"persistent"=>false,
	"capacity"=>array(
		"default"=>array(20,3),#默认的缓存数量，缓存20个，低于3个的时候充值
	),
	"parents"=>array($server_master,$server_backup),#当该节点没有id的时候去哪里拿id,按权值顺序挨个尝试
	"weight"=>50, #可选，默认1，当与其他server组成集群时排序用，数字越大越优先
	"max_retry"=>4, #可选，默认2，不需要设定太大，某小概率事件的尝试次数，细节请咨询开发者
);


#master
$server_list=array($server_master);
$id=lib_get_auto_id($server_list,$key,$count);

#slave
$server_list=array($server_current,$server_backup);
$id=lib_get_auto_id($server_list,$key,$count);

#client
$server_list=array($server1,$server2);
$id=lib_get_auto_id($server_list,$key);

http service refill_auto_id  每分钟检查一次，如果id不够了就去补充
*/


#===1.0.1 add
function auto_id_mysql_connect($host,$usr,$pass,$persistent){
	if($persistent==2){
		$conn=@mysql_connect($host,$usr,$pass);
	}elseif($persistent){
		$conn=@mysql_pconnect($host,$usr,$pass);
	}else{
		$conn=@mysql_connect($host,$usr,$pass,true);
	}
	if(!$conn){
		return false;
	}
	return $conn;
}

#===1.0.3 add
function auto_id_mysql_close($conn,$persistent){
	if($persistent==2){
		
	}elseif($persistent){
		
	}else{
		@mysql_close($conn);
	}
	return false;
}


#传给usort做比较
function cmp_by_weight_desc($a,$b){
	$a_weight= @$a["weight"]?$a["weight"]:1;
	$b_weight= @$b["weight"]?$b["weight"]:1;
	if($a_weight == $b_weight) {
		return 0;
	}
	return ($a_weight > $b_weight) ? -1 : 1;
}

#if succ  return id  else return false
#从master中得到id
function get_auto_id_master($server,$key=0,$count=1){
	if (!is_numeric($key)) {
		die(__LINE__."@".__FILE__.":"."BUG");
		return false;
	}
	$key=intval($key);
	$count=intval($count);
	if(!($key>=0 && $key<65536 && $count>=1 && $count<16777216)) {
		die(__LINE__."@".__FILE__.":"."BUG");
		return false;
	}
	$conn=auto_id_mysql_connect($server['host'],$server['usr'],$server['pass'],@$server['persistent']);
	if(!$conn){
		return false;
	}
	if(!@mysql_query("update {$server['auto_id_table']} set id=last_insert_id(id+$count) where k=$key",$conn)){
		auto_id_mysql_close($conn,@$server['persistent']);
		return false;
	}
	if(@mysql_affected_rows($conn)==0){
		@mysql_query("insert ignore into {$server['auto_id_table']}(k,id) values ($key,0)",$conn);
		@mysql_query("update {$server['auto_id_table']} set id=last_insert_id(id+$count) where k=$key",$conn);
	}
	$id=@mysql_insert_id($conn);
	auto_id_mysql_close($conn,@$server['persistent']);
	return $id;
}

#从master中得到键值的列表
function get_key_list_master($server){
	$conn=auto_id_mysql_connect($server['host'],$server['usr'],$server['pass'],@$server['persistent']);
	if(!$conn){
		return false;
	}
	
	$result=@mysql_query("select k from {$server['auto_id_table']}",$conn);
	if(!$result){
		auto_id_mysql_close($conn,@$server['persistent']);
		return false;
	}
	$r=array();
	while (($row = mysql_fetch_array($result, MYSQL_NUM))!=false) {
		array_push($r,$row[0]);
	}
	mysql_free_result($result);
	auto_id_mysql_close($conn,@$server['persistent']);
	return $r;
}

#简单http协议实现web service
function simple_http_call($url,$method,$auth,$param,$timeout=15){
	if(isset($param["method"]) || isset($param["auth"]) ){
		die(__LINE__."@".__FILE__.":"."BUG");
		return false;
	}
	$param["method"]=$method;
	$param["auth"]=$auth;#交互密码
	$dest_url=$url."?".http_build_query($param);
	$use_curl=extension_loaded("curl");
	if($use_curl){
		$curl=curl_init();
		curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,false);
		//curl_setopt($curl,CURLOPT_COOKIEJAR,null);
		curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
		curl_setopt($curl,CURLOPT_TIMEOUT,15);
		curl_setopt($curl,CURLOPT_FOLLOWLOCATION,false);
		curl_setopt($curl,CURLOPT_AUTOREFERER,false);
		curl_setopt($curl,CURLOPT_USERAGENT,"simple_http_call");
		curl_setopt($curl,CURLOPT_TIMEOUT,$timeout);
		curl_setopt($curl,CURLOPT_URL,$dest_url);
		curl_setopt($curl,CURLOPT_HTTPGET,1);
		$content=curl_exec($curl);
		if(curl_getinfo($curl,CURLINFO_HTTP_CODE)!=200){
			$content==false;
		}
		curl_close($curl);
	}else{
		$content=@file_get_contents($dest_url);
	}
	if($content===false){
		return false;//调用不成功
	}
	$ret=@json_decode($content,true);
	if(is_array($ret) && @$ret["code"]==200 && @$ret["auth"]==$auth && array_key_exists("data",$ret)){
		return $ret["data"];
	}else{
		return false;//返回数据无法解析
	}
}

#通过web service得到id
function get_auto_id_http($server,$key=0,$count=1){
	if(isset($server["timeout"])){
		$ret=simple_http_call($server["url"],"get_auto_id",$server["auth"],array("key"=>$key,"count"=>$count),$server["timeout"]);
	}else{
		$ret=simple_http_call($server["url"],"get_auto_id",$server["auth"],array("key"=>$key,"count"=>$count));
	}
	return ($ret?$ret["id"]:false);
}



#通过web service得到键值的列表
function get_key_list_http($server){
	$ret=simple_http_call($server["url"],"get_key_list",$server["auth"],array());
	return ($ret?$ret["key_list"]:false);
}

#从slave中得到id,返回-1如果余量不够
function get_auto_id_slave_mysql($server,$key=0,$count=1){
	if (!is_numeric($key)) {
		die(__LINE__."@".__FILE__.":"."BUG");
		return false;
	}
	$key=intval($key);
	$count=intval($count);
	if(!($key>=0 && $key<65536 && $count>=1 && $count<8388608)) {
		die(__LINE__."@".__FILE__.":"."BUG");
		return false;
	}
	$conn=auto_id_mysql_connect($server['host'],$server['usr'],$server['pass'],@$server['persistent']);
	if(!$conn){
		return false;
	}
	if(!@mysql_query("update {$server['auto_id_table']} set id=last_insert_id(id+$count),num=num-$count where k=$key and num >= $count",$conn)){
		auto_id_mysql_close($conn,@$server['persistent']);
		return false;
	}
	if(@mysql_affected_rows($conn)==0){
		auto_id_mysql_close($conn,@$server['persistent']);
		return "-1";
	}
	$id=@mysql_insert_id($conn);
	auto_id_mysql_close($conn,@$server['persistent']);
	return $id;
}

#从slave中得到某个key余下的数量
function get_auto_id_num_slave_mysql($server,$key){
	if (!is_numeric($key)) {
		die(__LINE__."@".__FILE__.":"."BUG");
		return false;
	}
	$key=intval($key);
	if(!($key>=0 && $key<65536)) {
		die(__LINE__."@".__FILE__.":"."BUG");
		return false;
	}
	
	$conn=auto_id_mysql_connect($server['host'],$server['usr'],$server['pass'],@$server['persistent']);
	if(!$conn){
		return false;
	}
	$result=@mysql_query("select num from {$server['auto_id_table']} where k=$key",$conn);
	if(!$result){
		auto_id_mysql_close($conn,@$server['persistent']);
		return false;
	}
	$row = mysql_fetch_array($result, MYSQL_NUM);
	auto_id_mysql_close($conn,@$server['persistent']);
	return ($row?$row[0]:"0");
}

#在slave上设置当前的id
function set_current_auto_id_slave_mysql($server,$key,$id,$count){
	if (!is_numeric($key)) {
		die(__LINE__."@".__FILE__.":"."BUG");
		return false;
	}
	$key=intval($key);
	$id=intval($id);
	$count=intval($count);
	if(!($key>=0 && $key<65536 && $id>=1  && $id<2147483648 && $count>=1 && $count<8388608 && $id>=$count)) {
		die(__LINE__."@".__FILE__.":"."BUG");
		return false;
	}
	$conn=auto_id_mysql_connect($server['host'],$server['usr'],$server['pass'],@$server['persistent']);
	if(!$conn){
		return false;
	}
	if(!@mysql_query("replace into {$server['auto_id_table']}(k,id,num) values($key,$id-$count,$count)",$conn)){
		auto_id_mysql_close($conn,@$server['persistent']);
		return false;
	}
	auto_id_mysql_close($conn,@$server['persistent']);
	return true;
}

#从slave获得id
function get_auto_id_slave($server,$key=0,$count=1){
	$max_retry=@$server["max_retry"]?$server["max_retry"]:2;
	for($i=$max_retry;$i-->0;){
		$id=get_auto_id_slave_mysql($server,$key,$count);
		if($id===false){
			return false;//当前表出问题 退出
		}elseif($id=="-1"){//当前表没数据，要去母节点充值
			
			if($i>0){#如果不尝试了 那也没必要充值了
				
				$capa=(isset($server["capacity"][$key])?$server["capacity"][$key]:$server["capacity"]["default"]);
				$new_count=$count+$capa[0]-1;//需求的ID数 加上容量-1
				$parents=@$server["parents"]?$server["parents"]:array();
				
				if(refill_auto_id_slave($server,$parents,$key,$new_count,$new_count)){
					continue;
				}
			}
			return false;//没有ID 而且母节点没有联通，没办法了
		}else{
			return $id;//成功
		}
	}
	return false;//充值的次数超过限制
}

#slave上从$server_parent拿取把缓存充满
function refill_auto_id_all_slave($server,$server_parent,$allow_ids){//为了防止系统结构混乱，cron充值只能从一个父节点冲，而且该父节点必须是http类型
	$key_list=get_key_list_http($server_parent);
	if($key_list===false){
		return false;
	}
	#===1.0.1 add
	if($allow_ids!==null){
		$key_list=array_intersect($key_list,$allow_ids);
	}
	#===end
	foreach($key_list as $key){
		$capa=(isset($server["capacity"][$key])?$server["capacity"][$key]:$server["capacity"]["default"]);
		if(!refill_auto_id_slave($server,array($server_parent),$key,$capa[0],$capa[1])){
			return false;
		}
	}
	return true;
}

#slave上从$server_parent拿取把某个键值的缓存充满
function refill_auto_id_slave($server,$server_parents,$key,$capacity,$min_num){//容量不够的时候增加容量
	$num=get_auto_id_num_slave_mysql($server,$key);
	
	
	if($num===false){
		return false;
	}	
	if($num < $min_num || $num==0){
		
		
		$new_id   = lib_get_auto_id($server_parents,$key,$capacity);
		if($new_id ===false){
		
			return false;
		}
		if(!set_current_auto_id_slave_mysql($server,$key,$new_id,$capacity)){
			return false;
		}
	}
	return true;
}

#总的得到 lib_get_auto_id
function lib_get_auto_id($server_list,$key=0,$count=1){
	shuffle($server_list);
	usort($server_list,"cmp_by_weight_desc");
	foreach($server_list as $server){
		if($server["auto_id_type"]=="master"){
			$ret=get_auto_id_master($server,$key,$count);
			if($ret!==false){
				return $ret;
			}
			continue;
		}elseif($server["auto_id_type"]=="slave"){
			$ret=get_auto_id_slave($server,$key,$count);
			if($ret!==false){
				return $ret;
			}
			continue;
		}elseif($server["auto_id_type"]=="http"){
			$ret=get_auto_id_http($server,$key,$count);
			if($ret!==false){
				return $ret;
			}
			continue;
		}else{
			die(__LINE__."@".__FILE__.":"."BUG");
		}
	}
	return false;
}
